﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Core.LockStep
{
    public interface IPoolObj
    {
        void Clear();
    }

    internal static class PoolTools
    {
        private static Dictionary<Type, IPoolObjContainer> pools = new Dictionary<Type, IPoolObjContainer>();

        private static Dictionary<Type, ReferenceContainer> referencePools = new Dictionary<Type, ReferenceContainer>();

        private static int objId;

        public static int GetNextObjId()
        {
            objId++;
            return objId;
        }

        public static T Pop<T>() where T : IPoolObj, new()
        {
            return (T)Pop(typeof(T));
        }

        public static IPoolObj Pop(Type objType)
        {
            if (!pools.TryGetValue(objType, out var container))
            {
                var containerType = typeof(PoolObjContainer<>);
                containerType = containerType.MakeGenericType(objType);
                container = (IPoolObjContainer)Activator.CreateInstance(containerType);
                pools[objType] = container;
            }

            return container.Pop();
        }

        public static void Push(IPoolObj obj)
        {
            if (obj == null)
                return;

            if (!pools.TryGetValue(obj.GetType(), out var pool))
                return;

            pool.Push(obj);
        }

        public static void PopReference<T>(int newId, T obj) where T : IReferenceData
        {
            if (obj == null)
                return;

            var objType = obj.GetType();

            if (!referencePools.TryGetValue(objType, out var pool))
                return;

            pool.Pop(newId, obj);
        }

        public static void PushReference<T>(T obj) where T : IReferenceData
        {
            if (obj == null)
                return;

            var objType = obj.GetType();
            if (!referencePools.TryGetValue(objType, out var pool))
            {
                pool = new ReferenceContainer();
                referencePools[objType] = pool;
            }

            pool.Push(obj);
        }

        public static void RecycleAllReferences()
        {
            foreach (var item in referencePools.Values)
                item.RecycleAll();
        }

        public static void Clear()
        {
            objId = 0;
            pools.Clear();
            referencePools.Clear();
        }
    }

    public interface IPoolObjContainer
    {
        IPoolObj Pop();
        void Push(IPoolObj obj);
        void Clear();
    }

    public class PoolObjContainer<T> : IPoolObjContainer where T : IPoolObj, new()
    {
        public Stack<T> pool = new Stack<T>();

        public IPoolObj Pop()
        {
            T result = default;

            if (pool.Count == 0)
            {
                result = new T();
            }
            else
            {
                result = pool.Pop();
            }

            return result;
        }

        public void Push(IPoolObj obj)
        {
            obj.Clear();
            pool.Push((T)obj);
        }

        public void Clear()
        {
            pool.Clear();
        }
    }

    public class ReferenceContainer
    {
        public Dictionary<int, IReferenceData> objects = new Dictionary<int, IReferenceData>();

        public void Clear()
        {
            objects.Clear();
        }

        public void Pop(int newId, IReferenceData tryPopItem)
        {
            if (objects.TryGetValue(newId, out var obj))
            {
                objects.Remove(newId);

                if (tryPopItem.ReferenceId != obj.ReferenceId)
                {
                    objects[tryPopItem.ReferenceId] = obj;

                    // 交换数据
                    var tempId = obj.ReferenceId;
                    var tempObj = obj.ReferenceObj;

                    obj.ReferenceId = tryPopItem.ReferenceId;
                    obj.ReferenceObj = tryPopItem.ReferenceObj;

                    tryPopItem.ReferenceId = tempId;
                    tryPopItem.ReferenceObj = tempObj;
                }
            }
        }

        public void Push(IReferenceData obj)
        {
            objects[obj.ReferenceId] = obj;
        }

        public void RecycleAll()
        {
            foreach (var item in objects.Values)
            {
                try
                {
                    item.OnRecycle();
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }

                item.ReferenceId = PoolTools.GetNextObjId();
                item.ReferenceObj = null;
            }

            objects.Clear();
        }
    }
}